/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 2008-2011, Salzburg Research. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * - Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 * - Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 * - Neither the name of the KiWi Project nor the names of its contributors
 * may be used to endorse or promote products derived from this software
 * without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 * Contributor(s):
 * sschaffe
 *
 * File Info:
 * User: sschaffe
 * Date: 2011/01/06
 * Time: 11:46
 *
 * Project: KiWi2, kiwi-core module
 */

package kiwi.core.services.transaction;

import kiwi.core.api.transaction.Transaction;
import kiwi.core.api.transaction.TransactionService;
import kiwi.core.exception.transaction.TransactionNotActiveException;
import kiwi.core.qualifiers.transaction.Transactional;
import org.slf4j.Logger;

import javax.inject.Inject;
import javax.interceptor.AroundInvoke;
import javax.interceptor.Interceptor;
import javax.interceptor.InvocationContext;

/**
 * The TransactionInterceptor is used when a class or method is annotated with @Transactional and makes sure that the
 * call is wrapped inside a transaction begin and commit. In case an exception is thrown during method invocation,
 * the error is logged and the transaction is rolled back.
 *
 * <p/>
 * User: sschaffe
 */
@Transactional @Interceptor
public class TransactionInterceptor {

    @Inject
    private Logger log;

    @Inject
    private TransactionService transactionService;

    @AroundInvoke
    public Object manageTransaction(InvocationContext ctx) throws Exception {


        Transaction t = transactionService.getTransaction();

        if(log.isDebugEnabled()) {
             String cls = ctx.getMethod().getDeclaringClass().getName();
             String method = ctx.getMethod().getName();

             log.debug("invoking transactional method {}.{} with transaction {}",
                     new Object[] {
                             cls,method,t
                     });
         }


        if(t.isCreated()) {
            // we have a fresh transaction, so the current invocation is the outermost invocation and begins and
            // commits/rolls back the transaction
            transactionService.begin(t);

            try {

                Object result = ctx.proceed();

                // the method invocation was successful so we commit the transaction
                transactionService.commit(t);

                return result;

            } catch(Exception ex) {
                // the method invocation threw an exception, so we write to the log and abort and rollback the
                // transaction

                String cls = ctx.getMethod().getDeclaringClass().getName();
                String method = ctx.getMethod().getName();


                log.error("aborting and rolling back transaction {} because method invocation {}.{} threw an exception ({}) ",
                          new Object[]{
                             t, cls, method, ex.getMessage()
                          });

                transactionService.abort(t);
                transactionService.rollback(t);

                throw ex;
            }

        } else if(t.isActive()) {
            // the transaction has already been started either manually or by a surrounding @Transactional interceptor
            // so we just proceed with the call and in case of an exception set the state to aborted

            try {
                Object result = ctx.proceed();

                return result;

            } catch(Exception ex) {
              // the method invocation threw an exception, so we write to the log and abort  the
                // transaction

                String cls = ctx.getMethod().getDeclaringClass().getName();
                String method = ctx.getMethod().getName();


                log.error("aborting transaction {} because method invocation {}.{} threw an exception ({}) ",
                          new Object[]{
                             t, cls, method, ex.getMessage()
                          });

                transactionService.abort(t);

                throw ex;

            }
        } else {
            throw new TransactionNotActiveException("Transaction "+t+" exists but is not active; cannot proceed.");
        }

    }

}
